<?php

namespace RedMVC;

/**
 * Form class
 *
 * @category    RedMVC
 * @author      Jan Fischer, bitWorking <info@bitworking.de>
 */
class Form{

    const FORM_METHOD_POST      = 'post';
	const FORM_METHOD_GET       = 'get';
    
    protected $elements         = array();
    protected $attributes       = array();
    protected $callbacks        = array();
    protected $isCalled         = false;
    protected $callbackReturn   = null;
    protected $_hasErrors       = false;
    protected $_keepValues      = false;
    
    protected $method           = null;
    protected $action           = '';
    
    protected $template         = '<div class="formClass">{formHead}<table>{elements}</table>{formFoot}</div>';
    
    protected $descriptionSign  = '?';

    protected $_files           = array();

    public function __construct($method = self::FORM_METHOD_POST, $action = '', $attributes = array()){
        $this->setMethod($method);
        $this->setAction($action);
        $this->setAttributes($attributes);        
    }
    
    protected function formHead(){
        return '<form method="'.$this->method.'" action="'.$this->action.'"'.$this->buildAttributes($this->attributes).'>';
    }
    
    protected function formFoot(){
        return '</form>';
    }
    
    public function setMethod($method){
        $this->method = $method;
    }
    
    public function getMethod(){
        return $this->method;
    }
    
    public function setAction($action){
        $this->action = $action;
    }
    
    public function getAction(){
        return $this->action;
    }
    
    public function setAttributes(array $attributes){
        $this->attributes = $attributes;
    }
    
    public function addAttribute($attribute, $value){
        $this->attributes[$attribute] = $value;
    }
    
    public function getAttribute($attribute){
        if(isset($this->attributes[$attribute])){
            return $this->attributes[$attribute];
        }
        return null;
    }

    public function addElement(Form\Element\AElement $element){  
        $element->setForm($this);  
        $this->elements[$element->getVarname()] = $element;
        if($element instanceof Form\Element\File){
            $this->addFile($element);
        }
    }
    
    public function getElement($varname){
        if(array_key_exists($varname, $this->elements)){
            return $this->elements[$varname];
        }
        return null;
    }
    
    public function removeElement($varname){
        unset($this->elements[$varname]);
    }
    
    public function getElements(){
        return $this->elements;
    }
    
    public function setElements(array $elements){
        $this->elements = $elements;
    }

    public function addFile(Form\Element\File $file){
        $this->_files[$file->getVarname()] = $file;
    }

    public function getFiles(){
        return $this->_files;
    }

    public function getFile($varname){
        if(array_key_exists($varname, $this->_files)){
            return $this->_files[$varname];
        }
        return null;
    }
    
    public function setKeepValues($bool = true){
        $this->_keepValues = $bool;
    }
    
    public function getKeepValues(){
        return $this->_keepValues;
    }    
    
    /* element helper */
    
    public function hidden($varname, $value){
        $element = new Form\Element\Hidden($varname);
        $element->setValue($value);
        $this->addElement($element);
        return $element;
    }
    
    public function text($label, $varname, $attributes=array()){
        $element = new Form\Element\Text($varname);
        $element->setLabel($label);
        $element->setAttributes($attributes);
        $this->addElement($element);
        return $element;
    }
    
    public function password($label, $varname, $attributes=array()){
        $element = new Form\Element\Password($varname);
        $element->setLabel($label);
        $element->setAttributes($attributes);
        $this->addElement($element);
        return $element;
    }

    public function file($label, $varname, Form\File $file, $attributes=array()){
        $element = new Form\Element\File($varname);
        $element->setLabel($label);
        $element->setAttributes($attributes);
        $element->setFile($file);
        $this->addElement($element);
        return $element;
    }
    
    public function textarea($label, $varname, $attributes=array()){
        $element = new Form\Element\Textarea($varname);
        $element->setLabel($label);
        $element->setAttributes($attributes);
        $this->addElement($element);
        return $element;
    }
    
    public function checkbox($label, $varname){
        $element = new Form\Element\Checkbox($varname);
        $element->setLabel($label);
        $this->addElement($element);
        return $element;
    }
    
    public function select($label, $varname, $options, $attributes=array()){
        $element = new Form\Element\Select($varname);
        $element->setLabel($label);
        $element->setOptions($options);
        $element->setAttributes($attributes);
        $this->addElement($element);
        return $element;
    }
    
    public function multiSelect($label, $varname, $options, $attributes=array()){
        $element = new Form\Element\MultiSelect($varname);
        $element->setLabel($label);
        $element->setOptions($options);
        $element->setAttributes($attributes);
        $this->addElement($element);
        return $element;
    }
    
    public function radios($label, $varname, $options, $delimiter = '<br>'){
        $element = new Form\Element\Radios($varname);
        $element->setLabel($label);
        $element->setOptions($options);
        $element->setDelimiter($delimiter);
        $this->addElement($element);
        return $element;
    }
    
    public function submit($varname, $value, $attributes=array()){
        $element = new Form\Element\Submit($varname);
        $element->setValue($value);
        $element->setAttributes($attributes);
        $this->addElement($element);
        return $element;
    }
    
    public function reset($varname, $value, $attributes=array()){
        $element = new Form\Element\Reset($varname);
        $element->setValue($value);
        $element->setAttributes($attributes);
        $this->addElement($element);
        return $element;
    }
    
    public function html($id, $value){
        $element = new Form\Element\Html($id);
        $element->setValue($value);
        $this->addElement($element);
        return $element;
    }
    
    public function render(){
        Event\Dispatcher::fire(Event\Dispatcher::EVENT_FORM_RENDER, array($this));
    
        if(!$this->isCalled){
            $this->call();
        }
        $templateVars = array();        
        $elements = '';
        foreach($this->elements as $varname=>$element){
            $element->setForm($this);
            $elementTemplate    = $element->getTemplate();
            $elementTemplateVars= $element->getTemplateVars();           
            $elements           .= $this->parseTemplate($elementTemplate, $elementTemplateVars);
        }
        $templateVars['{elements}'] = $elements;
        $templateVars['{formHead}'] = $this->formHead();
        $templateVars['{formFoot}'] = $this->formFoot();        
        return $this->parseTemplate($this->getTemplate(), $templateVars);
    }
    
    public function setTemplate($template){
        $this->template = $template;
    }
    
    public function getTemplate(){
        return $this->template;
    }
    
    public function getTemplateVars(){
        return array('{formHead}'=>$this->formHead(), '{elements}'=>$this->render());
    }
    
    /* callback */
    
    public function addCallback($varname, $callbackFunction){
        $this->callbacks[$varname] = $callbackFunction;
    }
    
    public function call(){
        if($this->isCalled){
            return $this->callbackReturn;
        }    
        $this->isCalled = true;
        $request        = $this->getRequest();
        foreach($this->callbacks as $varname=>$callbackFunction) {
            if(array_key_exists($varname, $request)){
                if($this->isValid($request)){
                    $request        = $this->filter($request);
                    $callbackReturn = call_user_func_array($callbackFunction, Array($this, $request));
                    $this->callbackReturn = $callbackReturn;
                    Event\Dispatcher::fire(Event\Dispatcher::EVENT_FORM_CALLBACK, array($this, $request));
                    return $callbackReturn;
                }                
            }
        }
    }
    
    public function getCallbackReturn(){
        return $this->callbackReturn;
    }
    
    protected function isValid(&$request){
        $isValid = true;        
        foreach($this->elements as $varname=>$element){
            if(!array_key_exists($varname, $request)){
                // is always multiselect ?
                $request[$varname] = array();
            }            
            $value      = $request[$varname];
            $validate   = true;            
            if(!$element->isRequired()){
                $validate = false;
                $notEmpty = new Form\Validator\NotEmpty();
                if($notEmpty->isValid($value)){
                    $validate = true;
                }
            }            
            if($validate){
                $validators = $element->getValidators();
                foreach($validators as $validator){
                    if(!$validator->isValid($value, $request)){
                        $element->addError($validator->getError());
                        $isValid = false;
                    }
                }
            }
        }
        // copy files        
        foreach($this->_files as $file){
            $fileHelper = new Form\FileHelper();
            if(!$fileHelper->copy($file, !$isValid)){
                $isValid = false;
            }
        }
        $this->_hasErrors = !$isValid;
        return $isValid;
    }
    
    protected function filter($request){
        foreach($this->elements as $varname=>$element){
            if(!array_key_exists($varname, $request)){
                continue;
            }
            $value      = $request[$varname];
            $filters    = $element->getFilters();
            foreach($filters as $filter){
                $value = $filter->filter($value);
            }
            $request[$varname] = $value;
        }
        return $request;
    }
    
    public function setError($bool = true){
        $this->_hasErrors = $bool;
    }
    
    public function hasErrors(){
        return $this->_hasErrors;
    }
    
    /* helper */
    
    public function buildAttributes(array $attributes){
        $arr = array();
        foreach($attributes as $attr => $value){
            $arr[] = $attr.'="'.$value.'"';
        }
        return (count($arr)==0) ? '' : ' '.implode($arr, ' ');
    }
    
    public function parseTemplate($template, array $vars){
        return strtr($template, $vars);
    }
    
    protected function getRequest(){
        $request_ = ($this->method == self::FORM_METHOD_POST) ? $_POST : $_GET;
        $request = array();
        foreach($this->elements as $varname=>$element){
            if(array_key_exists($varname, $request_)){
                $request[$varname] = $request_[$varname];
            }            
        }        
        return $request;
    }
    
    public function getRequestValue($varname){
        $request = ($this->method == self::FORM_METHOD_POST) ? $_POST : $_GET;
        if(array_key_exists($varname, $request)){
            return $request[$varname];
        }
        return null;
    }
    
    public function clear(){
        $request = $this->getRequest();
        foreach($this->elements as $varname=>$element){
            if(array_key_exists($varname, $request)){
                if($this->method == self::FORM_METHOD_POST){
                    unset($_POST[$varname]);
                }
                else{       
                    unset($_GET[$varname]);
                }
            }
        }
    }
    
    public function setDescriptionSign($descriptionSign){
        $this->descriptionSign = $descriptionSign;
    }
    
    public function getDescriptionSign(){        
        return $this->descriptionSign;
    }
    
    public function setValues(array $formValues){
        foreach($formValues as $key => $value){
            if(array_key_exists($key, $this->elements)){
                $this->elements[$key]->setValue($value);
            }
        }
    }
    
    public function setElementsTemplate($template){
        foreach($this->elements as $varname=>$element){
            $element->setTemplate($template);
        }
    }

}